/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file parse.c
 * PyHP parser
 */

#include <apr_file_io.h>
#include <apr_strings.h>
#include <apr_tables.h>
#include <apr_mmap.h>
#include <libgen.h>

#include "parse.h"
#include "mod_pyhp.h"
#include "logging.h"
#include "config.h"

#define PYHP_OPENTAG "<?pyhp"
#define PYHP_OPENTAG_LEN 7

#define PYHP_CLOSETAG "?>"
#define PYHP_CLOSETAG_LEN 2

#define PYHP_INCLUDETAG "<?include"
#define PYHP_INCLUDETAG_LEN 9

static char *pyhp_parse_file_do(apr_pool_t *pool, const char *filename,
                                apr_table_t *stack, request_rec *r);

/**
 * Concatenates a text block with a substring of another text block.
 * if the second text block is declared as being python code is
 * appended as it is, if it is not declared as being python code
 * it is wrapped inside a pyhp.write() call.
 *
 * @param pool The memory pool from which to allocate memory
 * @param result The first text block where to append the second one
 * @param text The second text block to append to the first one
 * @param text_end Pointer to the end of the text substring, might be NULL
 *                 to include the full string
 * @param in_code The second text block is a python code block
 *
 * @return The new resulting string
 */
static char *pyhp_append_text(apr_pool_t *pool, char *result,
                               char const *text, char *text_end,
                               short in_code)
{
    if (!in_code)
        result = apr_pstrcat(pool, result, "\npyhp.write( '''", NULL);

    if (text_end)
        result = apr_pstrcat(pool, result,
                             apr_pstrndup(pool, text, text_end - text), NULL);
    else
        result = apr_pstrcat(pool, result, text, NULL);

    if (!in_code)
        result = apr_pstrcat(pool, result, "''')\n", NULL);

    return result;
}

/**
 * Parses the <?include filename?> tag substituting with the file content
 *
 * @param pool The memory pool to use to allocate temporary things
 * @param result The pointer to the text where to append the result of
 * the parsing
 * @param tag_begin The pointer to the <?include string
 * @param stack Inclusion stack, here will be saved the included files history
 * @param root The directory from which relative paths should be built
 *
 * @return The text resulting after the parsing
 */
static char *pyhp_parse_include(apr_pool_t *pool, char *result,
                                char *tag_position, apr_table_t *stack,
                                const char *root, request_rec *r)
{
    char *param_begin = tag_position + PYHP_INCLUDETAG_LEN;
    char *param_end = strstr(tag_position, PYHP_CLOSETAG);
    char *fname = NULL;
    unsigned shift = PYHP_CLOSETAG_LEN;

    if (!param_end)
        return NULL;

    while(isspace(*param_begin))
        ++param_begin;
    while(isspace(*(param_end-1)))
        --param_end;
    fname = apr_pstrndup(pool, param_begin, param_end - param_begin);
    // check if the path is absolute or relative
    if (param_begin[0] != '/')
        fname = apr_pstrcat(pool, root, "/", fname, NULL);

    return pyhp_parse_file_do(pool, fname, stack, r);
}

/**
 * Parses a string converting mixed html and python code inside
 * <?pyhp ... ?> tag to a python script printing out the html and executing
 * the python block.
 *
 * @param pool The memory pool from which to allocate memory
 * @param src The text to parse
 * @param stack The stack to parse to the inclusion function
 * @param root The directory from which relative paths should be built
 *
 * @return The resulting python code block
 */
static char *pyhp_parse_tag(apr_pool_t *pool, const char *src,
                            apr_table_t *stack, const char *root,
                            request_rec *r, const char *filename)
{
    char *tag_position, *inc_position;
    short in_code = 0;
    char const *txt_to_parse = src;
    char *result = "";

    while (txt_to_parse) {
        if (!in_code) {
	    tag_position = strstr(txt_to_parse, PYHP_OPENTAG);
	    inc_position = strstr(txt_to_parse, PYHP_INCLUDETAG);

            if ((tag_position) && ((tag_position < inc_position) || (!inc_position)) ) {

                result = pyhp_append_text(pool, result, txt_to_parse,
                                          tag_position, in_code);
                in_code = 1;
                txt_to_parse = tag_position + PYHP_OPENTAG_LEN;
            }
            else if ((inc_position) && ((inc_position < tag_position) || (!tag_position))  ) {
		tag_position = inc_position;

		result = pyhp_append_text(pool, result, txt_to_parse,
                                          tag_position, 0);
                txt_to_parse = strstr(txt_to_parse, PYHP_CLOSETAG);
                if (!txt_to_parse)
                    break;
                txt_to_parse += PYHP_CLOSETAG_LEN;

                tag_position = pyhp_parse_include(pool, result, tag_position,
                                                  stack, root, r);
                if (tag_position)
                    result = pyhp_append_text(pool, result, tag_position,
                                              NULL, 1);
            }
            else
                break;
        } else {
            tag_position = strstr(txt_to_parse, PYHP_CLOSETAG);
            if (!tag_position)
                break;

#if 0
            char const * cur = txt_to_parse;
            char * buffer =  apr_palloc(pool, tag_position - txt_to_parse + 1);
            int i;
            int buffer_pos = 0;
            int in_space_counter = 1;
            int on_new_line = 0;
            int space_count = 0;
            int line_count = 0;

            for (i = 0; i < tag_position- txt_to_parse; ++i) {
                if ( in_space_counter ) {
                    if ( (cur[i] == ' ') || (cur[i] == '\t') )
                        ++space_count;
                    else if ( (cur[i] == '\r') )
                        continue;
                    else if ( (cur[i] == '\n') ) {
                        ++line_count;
                        space_count = 0;
                    }
                    else {
                        in_space_counter = 0;
                        buffer[buffer_pos++] = cur[i];
                    }
                }
                else {
                    if (on_new_line) {
                        if ((cur[i] == ' ') || (cur[i] == '\t')) {
                            --on_new_line;
                            continue;
                        }
                        else if ((cur[i] == '\r') || (cur[i] == '\n'))
                            on_new_line = 0;
                        else {
                            pyhp_logf(r, PyHP_WARN, "%s:%u indent less of first "
                                      "line in pyhp tag", filename, line_count);
                            on_new_line = 0;
                        }
                    }

                    if (cur[i] == '\r')
                        continue;

                    buffer[buffer_pos++] = cur[i];
                    if (cur[i] == '\n') {
                        ++line_count;
                        on_new_line = space_count;
                    }
                }
            }

            buffer[tag_position - txt_to_parse] = '\0';
            result = apr_pstrcat(pool, result, buffer, NULL);
#endif

            result = pyhp_append_text(pool, result, txt_to_parse,
                                      tag_position, in_code);

            in_code = 0;
            txt_to_parse = tag_position + PYHP_CLOSETAG_LEN;
        }
    }

    result = pyhp_append_text(pool, result, txt_to_parse, NULL, in_code);

    return result;
}

/**
 * Really parses a file if it isn't inside the parsed files stack and appends
 * it to the parsed file stack.
 *
 * @param pool The memory pool from which to allocate memory
 * @param filename The path of the file to read
 * @param stack The stack where check the existance of the file and append it
 *
 * @return The result of the parsing, should be a valid python code block
 */
static char *pyhp_parse_file_do(apr_pool_t *pool, const char *filename,
                                 apr_table_t *stack, request_rec *r)
{
    struct stat info;
    apr_file_t *file;
    apr_mmap_t *file_content;
    apr_status_t rv;
    char *result = "";
    char *dir = dirname(apr_pstrdup(pool,filename));

    if (apr_file_open(&file, filename, APR_READ, APR_OS_DEFAULT, pool)
        != APR_SUCCESS)
        return NULL;

    if (apr_table_get(stack, filename))
        return NULL;

    apr_table_set(stack, filename, "1");

    lstat(filename, &info);

    if ((rv = apr_mmap_create(&file_content, file, 0, info.st_size,
                    APR_MMAP_READ, pool)) != APR_SUCCESS) {
	char buf[1024];
        pyhp_logf(r, PyHP_ERR, "%s :: %s size %d",
		  apr_strerror(rv, buf, 1024), filename, info.st_size);
	return NULL;
    }
    PyObject *sys_module = PyImport_ImportModule("sys");
    PyObject *sys_mdict = PyModule_GetDict(sys_module);
    PyObject *path = PyDict_GetItemString(sys_mdict, "path");
    PyObject *py_dir = PyString_FromString(dir);

    while (PySequence_Count(path, py_dir) != 0) {
        int pos = PySequence_Index(path, py_dir);
        PySequence_DelItem(path, pos);
    }

    if (!PyObject_CallMethod(path, "append", "s", dir))
        pyhp_logf(r, PyHP_ERR, "Error while appending %s to path", dir);

    result = pyhp_parse_tag(pool, file_content->mm, stack, dir, r, filename);

    //apr_table_unset(stack, filename);
    apr_mmap_delete(file_content);
    apr_file_close(file);

    return result;
}

/**
 * Reads a .pyhp file and parses it returning a valid python block to be
 * executed from mod_pyhp. (It simply sets up the environment and calls
 * pyhp_parse_file_do)
 *
 * @param r The request for which to parse the file
 * @param filename The path of the file to read
 *
 * @return The result of the parsing, should be a valid python code block
 */
char *pyhp_parse_file(PyHPData *pydata, request_rec *r, const char *filename)
{
    apr_table_t *include_stack = apr_table_make(r->pool, 10);
    return pyhp_parse_file_do(r->pool, filename, include_stack, r);
}

#ifdef PYHP_TEST
int main(int argc, char *argv [])
{
    apr_pool_t *pool;

    apr_initialize();
    apr_pool_create(&pool, NULL);

    printf("''' This class is for test purposes ONLY '''\n"
            "class my_pyhp ( object ):\n"
            "    def write ( self, s ):\n"
            "        print s\n"
            "\n"
            "_pyhp = my_pyhp ()\n"
        );

    printf("%s\n", pyhp_parse_tag(pool,
            "<html>\n"
            "<head>\n"
            "<title>Hello World</title>\n"
            "</head><body>\n"
            "<?pyhp\n"
            "t = 0\n"
            "while t < 5:\n"
            "    _pyhp.write ( \"Hello World\" )\n"
            "    t += 1\n"
            "?>\n"
            "</body></html>\n"
    //        "<?pyhp print 'Hello'?>\n"
        ));


    apr_pool_destroy(pool);
    apr_terminate();
}
#endif

